#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>

MODULE_LICENSE("Dual BSD/GPL");
#define PROC_DIR     "hello"
#define PROC_RD_FILE "rd_name" // readonly proc file
#define PROC_RW_FILE "rw_name"

static char* file_rw_data;           // proc rw file 内容从此处读取
static char* file_rd_data = "hello"; // proc rd file 内容从此处读取

static struct proc_dir_entry* p_dir = NULL;
static struct proc_dir_entry* p_name_rd = NULL;
static struct proc_dir_entry* p_name_rw = NULL;

ssize_t ProcFs_write_file(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
{
    long copyVal;

    // check user buffer
    if (unlikely(!access_ok(buf, count)))
        return -EFAULT;

    if (!IS_ERR_OR_NULL(file_rw_data)) {
        kfree(file_rw_data);
        file_rw_data = NULL;
    }

    file_rw_data = kmalloc(count + 1, GFP_NOFS); // +1 for zero-term
    if (unlikely(!file_rw_data)) {
        printk(KERN_ERR "kmalloc %ld failed\n", count + 1);
        return -EFAULT;
    }
    file_rw_data[count] = '\0'; // +1 for zero-term

    copyVal = __copy_from_user(file_rw_data, buf, count);
    if (copyVal != 0) {
        printk(KERN_ERR "Hello, world\n");
        return -EFAULT;
    }

    printk("hello_init: write proc file %s/%s success, count=%ld\n", PROC_DIR, PROC_RW_FILE, count);
    return count;
}

int ProcFs_read_rw_file(struct seq_file* file, void* p)
{
    seq_printf(file, "%s # (read and write file)\n", file_rw_data);
    return 0;
}

int ProcFs_read_rd_file(struct seq_file* file, void* p)
{
    seq_printf(file, "%s\n # (readonly file)\n", file_rd_data);
    return 0;
}

/*
 *	目的：不同的 proc 文件使用相同的 .open 函数
 *  需要在 proc_create_data 时保存 .show 到私有 data
 */
int ProcFs_open(struct inode* inode, struct file* file)
{
    /* newer kernels do no longer export struct proc_dir_entry, so the data field
       is only accessible through special kernel methods. */
    // 获取 proc_create_data 是传入的 data
    int (*show)(struct seq_file*, void*) = PDE_DATA(inode);
    return single_open(file, show, NULL);
}

static struct file_operations proc_rd_fops = {
    .open = ProcFs_open,
    .release = single_release,
    .read = seq_read,
    .llseek = seq_lseek,
};

static struct file_operations proc_rw_fops = {
    .open = ProcFs_open,
    .release = single_release,
    .read = seq_read,
    .write = &ProcFs_write_file,
    .llseek = seq_lseek,
};

static int hello_init(void)
{
    // 创建只读 proc dir
    p_dir = proc_mkdir(PROC_DIR, NULL);
    if (!p_dir) {
        printk(KERN_ALERT "Failed to create proc dir\n");
        return -EINVAL;
    }

    // 创建只读 proc file
    p_name_rd = proc_create_data(PROC_RD_FILE, S_IFREG | S_IRUGO, p_dir, &proc_rd_fops, &ProcFs_read_rd_file);

    if (!p_name_rd) {
        printk("hello_init: creat name node failed!!!\n");
        proc_remove(p_dir);
        return -EINVAL;
    }
    printk("hello_init: create %s/%s success\n", PROC_DIR, PROC_RD_FILE);

    // 创建只读 proc file
    p_name_rw = proc_create_data(
        PROC_RW_FILE, S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP, p_dir, &proc_rw_fops, &ProcFs_read_rw_file);

    if (!p_name_rw) {
        printk("hello_init: creat proc file %s/%s failed!!!\n", PROC_DIR, PROC_RW_FILE);
        proc_remove(p_dir);
        return -EINVAL;
    }
    printk("hello_init: creat %s/%s success\n", PROC_DIR, PROC_RW_FILE);

    return 0;
}

static void hello_exit(void)
{
    if (!IS_ERR_OR_NULL(file_rw_data)) {
        kfree(file_rw_data);
        file_rw_data = NULL;
    }
    proc_remove(p_dir); // 删除 dir 及其下所有子项
    printk("hello_exit: remove proc/%s success\n", PROC_DIR);
}

module_init(hello_init);
module_exit(hello_exit);
